パソコン活用研究ラピュタへの道(アセンブラ、DOS、Windows、旧型PCの活用研究)
8086CPUの基礎
======================================================================================
アセンブラに関する本も最近はあまり見かけなくなりました。かなり大きな書店でも、入手困難になりつつ
あります。一番入手しやすく、読みやすいのは「はじめて読む8086」(アスキー出版 蒲地
輝尚 著)
あたりかと思いますが、これも見つかりにくくなってきました。WEB上でもアセンブラ関連のサイトは
少ないようです。
とりあえず、アセンブラでプログラミングしてみようという方は、「プログラマの隠れ里」(おじさんの関連サイト
「パソコン活用研究5番街/リンク集」 にリンクはってあります)も覗いてみて下さい。アセンブラによる
32Bitアプリ、16Bitアプリ作成について詳しく解説されています。
おじさんのこのページではアセンブラで16Bitアプリを作成するのに最低限必要な基礎知識だけまとめて
おきたいと思います。先述の「プログラマの隠れ里」に素晴らしい解説があるので、まあ、省略してもよかった
んですが、何もないのも寂しいのでおじさんなりに。
======================================================================================
(1) 8086
8086というCPUは言わずと知れたPentiumの祖先であります。このCPUは互換性を保ったまま発展
してきたので、Pentiumで8086用の命令をそのまま実行できます。Windows上ではDOS窓や、
MS−DOSモードで8086用に作られたアセンブラプログラムを実行することができます。
(2) メモリー
8086がアクセスできるのは00000H(Hは16進数表記)からFFFFFHまでのメモリーです。すなわち
1MByteです。8086ではメモリーのある番地を指定するのに、ちょっと面倒なのですが、セグメントアドレス
オフセットアドレスという2つのアドレスを用いて指定します。
番地=(セグメントアドレス*16)+オフセットアドレス
ということになります。(セグメントアドレス、オフセットアドレスともにとれる範囲は0000HからFFFFHまで
となります。すなわち2byte=16bit分)
面倒なことですが、A5000Hというようには一発で直接指定はできません。(他のCPU,例えばモトローラ社の
68000などは一発で指定できる仕様になっている)
ジャンプ命令のJMPは JMP オフセットアドレス という書き方をしますが、
今仮に、セグメントアドレス(以下SEGと略す)が0000Hの時に、JMP 1000Hということは、
実際のジャンプ先の番地は、0H*16+1000H=1000Hとなります。
SEGが2000Hで JMP 1000Hなら
実際の番地は、2000H*16+1000H=20000H+1000H=21000Hとなります。
(*16進数で2000H*16(=10H)は20000Hです。)
SEGを指定するには、セグメントレジスタというレジスタを使います。8086では、主に
CS(コードセグメント)・・・実行コードのあるセグメントを指定する。
DS(データセグメント)・・・データのあるセグメントを指定する。
というセグメントレジスタを使います。(これは、また後で詳しくやります)
さて、賢明な方はお気づきの通り、この番地の指定の仕方の場合、同じ番地を指定するのに
いくとおりでもやり方があることになります。例えば、100Hを指定したい場合
SEG=10Hオフセットアドレス=0Hでもいいし、SEG=0Hオフセットアドレス=100H
でも良いわけです。従って、下手にCS,DSを設定すると、データ領域とコード領域が重なって、
データでマシン語コードをつぶして、暴走ということもありえるわけです。
(3) I/Oポート
コンピュータにはいろんな周辺機器が接続されます。最低限の仕様でもキーボード、マウスなどなど。
8086では周辺機器のデータの入出力ように上述のメモリーとは別にI/Oポートという、周辺機器専用
のポートを持っています。8086では0000HからFFFFHまで、すなわち64KByte分のポートが用意
されています。I/OポートへのアクセスはIN, OUTという専用の命令を使います。
(他のCPUでは、専用のI/Oポートを持たず、周辺機器を一般のメモリーの特定の番地に接続する
ものもある。)
(4) レジスタ
CPUの内部にある特殊なメモリーがレジスタです。レジスターはCPUの内部にあるため、メモリーより
高速にアクセスできます。
機械語の多くは、レジスターを操作するものです。機械語でプログラムするにはレジスターについての
知識が必須となりますので、8086のレジスターについてまとめておきます。具体的な使い方は実際に
機械語のプログラムを組みながら覚えていけば良いと思いますので、レジスタの種類だけ頭にいれて
おけばいいです。初めて機械語に触れる人は下記の部分を読んでもなんのことかわかんないと思いますが、
プログラムする時に理解できますので、ざっとみてそのまますすんで下さい。
AX | データの一時記憶 また各種演算で使用する |
BX | 特定のメモリを指定するポインタとして使用 |
CX | 繰り返し処理命令の回数を数えるカウンタとして使用 |
DX | データの一時記憶。 AXと組み合わせて32bitの乗除算で使用 |
これらの汎用レジスタはみな16bitです。それぞれ半分にして8Bitレジスタとしても使用できます。
AX --> AL,AH
BX --> BL,BH
CX --> CL,CH
DX --> DL,DH
CS | 機械語のコードのあるセグメントアドレス |
DS | データのあるセグメントアドレス |
ES | 第2のデータセグメントアドレス |
SS | スタックセグメントアドレス |
SI | 特定のメモリを指定するポインタとして使用 |
DI | これもメモリを指定するポインタとして。 転送命令等でSIを転送元アドレス、DIを転送先アドレスとして使用したりする。 |
BP | ベースポイインタ。SS内で使用 |
SP | スタックポインタ |
IP | 次に実行する機械語のオフセットアドレス |
(5) フラグレジスタ
これは、非常に重要なレジスタです。機械語で演算や比較命令等を実行した時に、結果が正か負か、
桁上がりがあったかどうか、比較の結果はどうだったか(等しい?大きい?小さい?)といった情報を
保持するレジスタです。機械語で条件分岐のプログラムを組む場合、このフラグレジスタの値を見て
分岐させることになります。
フラグレジスタの各1Bitに各種の状態がセットされ、各Bitが0(リセット)か1(セット)かで判断がなされます。
OF | オーバーフローフラグ | 符号付き演算で桁あふれが生じるとセット |
DF | ディレクションフラグ | ストリング操作命令におけるポインタの増減の方向 |
IF | インタラプトイネーブル | リセットすると外部割り込み受け付け不可になる |
TF | トラップフラグ | |
SF | サインフラグ | 演算結果が負ならセット |
ZF | ゼロフラグ | 演算結果がゼロ(あるいは比較の結果イコール)の時セット |
AF | 補助キャリー | |
PF | パリティーフラグ | 演算の結果、1となるBitが偶数個のときセット、奇数個ならリセット |
CF | キャリーフラグ | 演算の結果、桁上がりが生じるとセット |
とりあえず、DF, SF, ZF, CFをよく使いますので、これだけ覚えておいて下さい。
(6) 符号の表しかた
機械語でデータを扱う時、符号無しの時と符号ありの場合があります。
データが符号ありの場合、最上位Bitが1はら負、0なら正です。
例えば8bitデータの場合を示します
16進 | 2進 | 符号無しの場合 | 符号ありの場合 |
FFH | 11111111 | 255 | −1 |
FEH | 11111110 | 254 | −2 |
FDH | 11111101 | 253 | −3 |
80H | 10000000 | 128 | −128 |
7FH | 01111111 | 127 | 127 |
7EH | 01111110 | 126 | 126 |
1H | 00000001 | 1 | 1 |
符号付きでややこしいのはFFHが−1になる点です。
機械語の演算命令も符号あり、なしの両タイプがあります。両方の処理をちょっと見てみます
符号なし演算命令の場合
符号なしでFFH(255)+1H(1)を計算すると,紙の上では100H(256)となります。
しかしパソコンのメモリー上では次のようになります。
ある1Byteのメモリー上では、結果はオーバーフローが生じて00HとなりOFフラグがセットされます。
プログラム上ではOFフラグをみて結果は00Hではなく100Hと判定します。
符号あり演算命令の場合
符号あり演算ではFFH(−1)+1H(1)は、結果はやはり00Hとなりますが、今度はZFフラグがセット
されます。ZFフラグをみて本当の0だと判定します。
おじさんのように面倒くさがりな人には、これは本当にかったるい問題です。
ちなみに16bitデータの場合、符号ありではFFFFH(1111111111111111)が−1です。
(7) 8086の数値のメモリーへの置き方
例えば、453DHという2Byteのデータを1000H、1001H番地に格納したい、とします。
この場合、MOV [1000H], 453DH という命令(453DHを1000H、1001H番地に格納せよ)
を使いますが、実際のメモリーへの格納のされかたは次のようになります。
1000H | 3D |
1001H | 45 |
上位と下位のByteが逆になって格納されます。これ注意して下さい。
(8) スタック
汎用レジスタはたった4つしかないので、複雑な処理をする時に、いったん今のレジスタの値をどっかに
置いておいて、べつの処理にレジスタを一時的に使いたい、などということがあります。
自分で、どっか適当なアドレスを決めてレジスタの値をコピーしておいて、別の処理がおわり次第、レジスタ
に値を書き戻してもいいわけですが、コンピュータには便利なスタックという仕組みがあります。
PUSHという命令でデータをスタックに積み、POPでスタックからデータを取り出します。
スタックに積んだり、戻したりするデータは2Byte単位で行います。また、スタックで特有な点は、
スタックにデータをつむ場合、若い番地に向けて積んでいく点です。また、取り出す場合若い番地のデータ
から取り出します。(在庫管理的に言うと、先入れ後だし=後入れ先だし)
スタック領域は先述のSSレジスタで設定され、特殊レジスタのSP(スタックポインタ)が一番最後に積まれた
スタックの位置(番地)を示します。
簡単なスタック操作の例
レジスタの値が以下のようになっているとします。
AX:5F4DH
BX:985EH
SP:E000H
この時に
PUSH AX
PUSH BX
POP BX
POP AX
という命令を実行した場合の、SPの値とメモリーの値の状態は以下のようになります。
|
実際のデータの置かれかたは、先述の通り上位Byteと下位Byteが逆な点に注意して下さい。
また、スタックに新しいデータをつむ時、若い番地に向けて積まれていく点にも注意して下さい。
いちおう、アセンブラでプログラム組む時に必要となる最低限の基礎知識はこれにておしまい